package interview.eventSystem;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by richard on 6/3/14.
 */
public class Container {

    private static final int MAX_CLIENT_SIZE=5;
    private static final int MAX_SERVER_SIZE=10;

    /*
     use this for the container size, cannot use the server size, as existing race condition when add and remove
      */
    private AtomicInteger count = new AtomicInteger(0);

    private List<Client> clientList = new ArrayList<>();
    private List<UpperServer> serverList = new ArrayList<>();

    List<Event> container;
    Lock getLock = new ReentrantLock();
    Lock putLock = new ReentrantLock();
    Condition getCondition = getLock.newCondition();
    Condition putCondition = putLock.newCondition();
    int size = 0;
    boolean isLimit = false;



    public void registerClient(Client client){//don't use this method when only one consumer can consume
        if(getClientList().size()==MAX_CLIENT_SIZE){
            throw new IllegalArgumentException("exceed the client size!");

        }
        getClientList().add(client);
        client.setContainer(this);
    }

    public void registerServer(UpperServer server){
        if(getServerList().size()==MAX_SERVER_SIZE){
            throw new IllegalArgumentException("exceed the server size!");

        }
        getServerList().add(server);
        server.setContainer(this);
    }

    public Container(int size, boolean isLimit) {
        this.size = size;
        this.isLimit = isLimit;
        container = new ArrayList<>(size);
    }

    public Container(){
        container = new ArrayList<>();
    }

    public Event getEvent() throws InterruptedException {
        Event event=null;
        int c=count.get();
        try{
            getLock.lock();
            c=count.get();
            if(c==0){
                waitWhenEmpty();
            }
            if(count.decrementAndGet()>1){
                notifyNotEmpty();
            }
            event = container.remove(0);


        } catch (InterruptedException e) {
            throw  new InterruptedException("Interrupted!");
        }
        finally {
            getLock.unlock();
        }
        if(c==size)
        notifyNotFull();
        return event;
    }

    public void addEvent(Event event) throws InterruptedException {
        int c =0;
        try{
            putLock.lock();
            c=count.get();
            if(c>=size&&isLimit){
                waitWhenFull();
            }
            if(count.getAndIncrement()+1<size){
                notifyNotFull();
            }
            add(event);
        } catch (InterruptedException e) {
            throw  new InterruptedException("Interrupted!");
        }
        finally {
            putLock.unlock();
        }
        if(c==0)//in order to avoid the other thread modify the init 'c' to zero, after add the entity we resent the notify again
        notifyNotEmpty();
    }

    private void  add(Event event){
        System.out.println("Container add a event from server :" + event.getMessage() + " current server size is :" + container.size());
        container.add(event);
    }

    private void waitWhenFull() throws InterruptedException {
        System.out.println("Container is full with size :" + container.size());
        try{
            putLock.lock();
            putCondition.await();
        }
        finally {
            putLock.unlock();
        }

    }

    private void waitWhenEmpty() throws InterruptedException {
        System.out.println("Container is empty with size :" + container.size());
        try{
            getLock.lock();
            getCondition.await();
        }
        finally {
            getLock.unlock();
        }

    }

    private void notifyNotFull() {
        try{
            putLock.lock();
            putCondition.signal();
        }
        finally {
            putLock.unlock();
        }

    }

    private void notifyNotEmpty(){
        try{
            getLock.lock();
            getCondition.signal();
        }
        finally {
            getLock.unlock();
        }

    }

    public List<Client> getClientList() {
        return clientList;
    }

    public List<UpperServer> getServerList() {
        return serverList;
    }
}
